package es.udima.geo.geoserver.rest;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import es.udima.geo.geoserver.entities.GpsData;
import es.udima.geo.geoserver.entities.GpsDataFilter;
import es.udima.geo.geoserver.entities.GpsDataRetrieveResult;

@Path("/gps")
public class GpsUserController {

    private static Map<String, List<GpsData>> gpsDataEmulation = new HashMap<String, List<GpsData>>();

    private static long gpsLapseTime = 2000;

    static {
	int i = 1;

	String uid1 = "uid001";
	List<GpsData> gpsData1 = new ArrayList<GpsData>();

	Calendar cal1 = Calendar.getInstance();
	cal1.set(2014, 5, 11, 10, 11, 0);
	Date date1 = cal1.getTime();
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.394806, -3.639897));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.395415, -3.639655));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.395778, -3.639425));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.395778, -3.639425));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.395778, -3.639425));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.395778, -3.639425));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.396175, -3.639264));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.396787, -3.638861));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.398046, -3.638025));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.398765, -3.637542));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.399402, -3.637129));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400039, -3.636721));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400754, -3.636249));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.400938, -3.636104));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.401269, -3.635879));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.401931, -3.635450));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.402703, -3.634945));

	cal1.set(2014, 5, 14, 12, 10, 20);
	date1 = cal1.getTime();
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.436585, -3.689745));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435679, -3.689102));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435197, -3.689134));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435197, -3.689134));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435197, -3.689134));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435197, -3.690121));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435181, -3.691140));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435181, -3.691140));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435181, -3.691140));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435181, -3.691140));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435181, -3.691140));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435172, -3.691451));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435148, -3.692116));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435156, -3.692835));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435107, -3.694015));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435107, -3.694766));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435213, -3.694831));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435524, -3.694809));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435809, -3.694777));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.435916, -3.694788));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.436112, -3.694777));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.436536, -3.694691));
	gpsData1.add(new GpsData(new Date(date1.getTime() + gpsLapseTime * i++), 40.436655, -3.694815));

	gpsDataEmulation.put(uid1, gpsData1);
    }

    /**
     * UserId in filter is a mandatory parameter. If no date filter is set it
     * will return the last gps data of the last day the user sent data. If date
     * filter is for more than MAX_DAYS_TO_RETRIVE_GPS_DATA it will return an
     * error.
     * 
     * @param filter
     * @return
     */
    @POST
    @Path("/user")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public GpsDataRetrieveResult readUserGpsData(GpsDataFilter filter) {
	// TODO filter date control

	String userId = filter.getUserId();
	Date initDate = filter.getInitDate();
	Date endDate = filter.getEndDate();

	List<GpsData> gpsData = gpsDataEmulation.get(userId);

	if (initDate == null && endDate == null) {
	    // no dates especified. We'll retrieve the last day found in gps
	    // user data

	}

	if (gpsData != null) {
	    calculateSpeedData(gpsData);
	}

	GpsDataRetrieveResult res = new GpsDataRetrieveResult();
	res.setUserId(userId);
	res.setInitDate(initDate);
	res.setEndDate(endDate);
	res.setGpsData(gpsData);

	return res;
    }

    /**
     * 
     * @param gpsData
     */
    private void calculateSpeedData(List<GpsData> gpsData) {
	GpsData gdPrev = null;
	for (GpsData gd : gpsData) {
	    if (gdPrev == null) {
		gdPrev = gd;
	    } else {
		calculateSpeedData(gdPrev, gd);
		gdPrev = gd;
	    }
	}
    }

    /**
     * Calculate the speed data for the gd2 when coming from gd1
     * 
     * @param gd1
     * @param gd2
     */
    private void calculateSpeedData(GpsData gd1, GpsData gd2) {

	double distance = calculateDistance(gd1.getLat(), gd1.getLon(), gd1.getLat(), gd2.getLon()) * 1000;
	double time = gd2.getFetchTime().getTime() - gd1.getFetchTime().getTime();
	double speed = (distance / 1000) / (time / (3600 * 1000));

	gd2.setDistanceFromPrevious(distance);
	gd2.setLapseTimeFromPrevious(time);
	gd2.setSpeedFromPrevious(speed);
    }

    /**
     * Formula used returning distance in km for gps coordinates in decimal
     * notation: Distancia = 6371 * ACOS(COS(Lat1) * COS(Lat2) * COS(Long2 -
     * Long1) + SIN(Lat1) * SIN(Lat2))
     * 
     * having: Lat1=(LATITUD1*3.141592654)/180 Lat2=(LATITUD2*3.141592654)/180
     * Long1=(LONGITUD1*3.141592654)/180 Long2=(LONGITUD2*3.141592654)/180
     * 
     * Extracted from: http://www.xoneisp.com/foro/showthread.php?tid=294
     * 
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
	double la1 = lat1 * Math.PI / 180;
	double lo1 = lon1 * Math.PI / 180;
	double la2 = lat2 * Math.PI / 180;
	double lo2 = lon2 * Math.PI / 180;

	double distance = 6371 * Math.acos(Math.cos(la1) * Math.cos(la2) * Math.cos(lo2 - lo1) + Math.sin(la1) * Math.sin(la2));

	return distance;
    }

}
